Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Jan 26, 2026

修复会话存档 API 使用错误 secret 的问题

问题分析

  • 理解问题:会话存档 API 需要使用会话存档 secret 获取 access token,而不是应用 secret
  • 探索代码结构:已完成
  • 制定解决方案

实施计划

  • 在 WxCpConfigStorage 中添加会话存档 access token 相关方法
  • 在 WxCpDefaultConfigImpl 中实现会话存档 access token 存储
  • 在 WxCpRedisConfigImpl 中添加会话存档 access token 方法(已修复NPE问题)
  • 在 WxCpService 接口中添加获取会话存档 access token 的方法
  • 在所有 WxCpService 实现类中实现 getMsgAuditAccessToken 方法
  • 修改 WxCpMsgAuditServiceImpl 使用会话存档专用的 access token
  • 改进单元测试以增加真实逻辑覆盖

代码审查反馈修复

已全面处理所有代码审查反馈:

  1. ✅ 修复了 WxCpRedisConfigImplgetMsgAuditAccessTokenLock() 返回 null 导致的 NPE 问题
  2. ✅ 改进了测试代码,增加了对实际 token 生命周期管理逻辑的测试覆盖
  3. ✅ 修复了测试代码中使用 var 类型声明的兼容性问题
  4. ✅ 改进了代码注释的准确性,明确说明了本地锁在多实例部署时的限制
  5. ✅ 移除了测试中的 Thread.sleep(),提高了测试的稳定性和可靠性
  6. ✅ 重构测试代码,提取公共的 token 过期检查和 secret 验证逻辑
  7. ✅ 改进了 Redis 配置类的文档,添加了对分布式锁实现方案的具体建议

验证

  • 编译代码:通过
  • 运行 checkstyle:通过
  • 代码审查:已处理所有评论
  • 所有代码质量问题已修复

使用示例

WxCpDefaultConfigImpl config = new WxCpDefaultConfigImpl();
config.setCorpId("your-corp-id");
config.setCorpSecret("your-app-secret");
config.setMsgAuditSecret("your-msgaudit-secret");  // 配置会话存档secret

// 会话存档API将自动使用专用access token
List<String> userIds = msgAuditService.getPermitUserList(null);

注意事项

对于使用 WxCpRedisConfigImpl 的用户,在多实例部署场景下,建议自行实现分布式锁机制(如使用 Redisson 或 Spring Integration 提供的 Redis 分布式锁)或使用其他支持分布式的配置存储实现。

Original prompt

This section details on the original issue you should resolve

<issue_title>weixin-java-cp 4.8.0版本中会话存档开发的bug</issue_title>
<issue_description>模块weixin-java-cp
版本4.8.0
虽然WxCpConfigStorage类中区分了应用secret和会话secret,但是获取开启会话存档的员工集时并没有使用会话secret,还是使用了应用secret,导致请求返回没有权限的问题
方法如图:

Image 配置如图: Image debug位置如图

Image</issue_description>

Comments on the Issue (you are @copilot in this section)

Custom agent used: 全部用中文
需要用中文,包括PR标题和分析总结过程


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Copilot AI and others added 2 commits January 26, 2026 14:53
- 在WxCpServiceApacheHttpClientImpl中实现getMsgAuditAccessToken方法
- 在WxCpServiceHttpComponentsImpl中实现getMsgAuditAccessToken方法
- 在WxCpServiceOkHttpImpl中实现getMsgAuditAccessToken方法,使用try-with-resources正确关闭Response
- 在WxCpServiceJoddHttpImpl中实现getMsgAuditAccessToken方法

所有实现遵循以下设计:
- 使用Lock机制和双重检查确保线程安全
- 使用会话存档secret(msgAuditSecret)获取access token
- 将token存储到updateMsgAuditAccessToken()
- 当msgAuditSecret未配置时抛出WxErrorException
- 保持各实现类原有的HTTP客户端使用风格和代理支持

新增单元测试WxCpServiceGetMsgAuditAccessTokenTest验证实现正确性,
包括正常场景和异常场景(secret未配置或为空)的测试。
Co-authored-by: binarywang <1343140+binarywang@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix session archive bug in weixin-java-cp 4.8.0 修复会话存档API使用错误secret导致权限异常的问题 Jan 26, 2026
Copilot AI requested a review from binarywang January 26, 2026 15:01
@binarywang binarywang marked this pull request as ready for review January 27, 2026 01:20
Copilot AI review requested due to automatic review settings January 27, 2026 01:20
@augmentcode
Copy link

augmentcode bot commented Jan 27, 2026

🤖 Augment PR Summary

Summary: 本 PR 修复企业微信“会话存档”相关接口错误使用应用 secret 获取 access_token,导致调用返回“无权限”的问题。

Changes:

  • WxCpConfigStorage 增加会话存档专用 access_token 的获取、过期判断、更新与锁接口
  • WxCpDefaultConfigImpl 新增会话存档 token 的本地存储与过期时间管理;WxCpRedisConfigImpl 补充对应方法(当前为存根)
  • WxCpService 新增 getMsgAuditAccessToken(boolean forceRefresh) 统一入口
  • 各 HTTP 客户端实现(Apache/HttpComponents/OkHttp/Jodd/默认实现)增加会话存档 token 拉取逻辑,并用锁避免并发重复刷新
  • WxCpMsgAuditServiceImplgetPermitUserList/getGroupChat/checkSingleAgree 改为使用会话存档专用 token 发起请求
  • 测试侧:补充接口适配(Base 测试桩增加新方法),并新增 getMsgAuditAccessToken 相关测试类

Technical Notes: 会话存档 token 与普通 token 分离管理,默认实现采用“双重检查 + 显式锁”避免重复刷新,并沿用“预留 200 秒”提前过期策略。

🤖 Was this summary useful? React with 👍 or 👎

Copy link

@augmentcode augmentcode bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review completed. 4 suggestions posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

}
String responseContent = this.cpService.post(apiUrl, jsonObject.toString());
// 使用不自动添加access token的post方法
String responseContent = this.cpService.postWithoutToken(urlWithToken, jsonObject.toString());
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里通过 postWithoutToken + 手动拼接 access_token 绕开了 BaseWxCpServiceImpl.execute(...) 的 token 失效自动刷新/重试逻辑;如果会话存档 token 被提前失效(未到本地过期时间),这些接口可能会直接失败而不会触发刷新。

Other Locations
  • weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMsgAuditServiceImpl.java:328
  • weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMsgAuditServiceImpl.java:341

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎

try (Response response = client.newCall(request).execute()) {
resultContent = response.body().string();
} catch (IOException e) {
log.error(e.getMessage(), e);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里捕获 IOException 只记录日志但继续向下执行,resultContent 可能为 null(或不完整),随后 WxError.fromJson(resultContent, ...) 可能触发 NPE/解析异常并掩盖真实网络错误。

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎


@Override
public Lock getMsgAuditAccessTokenLock() {
return null;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getMsgAuditAccessTokenLock() 返回 null 会导致各个 getMsgAuditAccessToken(...) 实现里 lock.lock() 直接 NPE;这会让 Redis 配置下的会话存档能力以“空指针”方式失败而不是可识别的异常。

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎

// 创建一个模拟实现,不实际调用HTTP请求
WxCpServiceApacheHttpClientImpl service = new WxCpServiceApacheHttpClientImpl() {
@Override
public String getMsgAuditAccessToken(boolean forceRefresh) throws WxErrorException {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个测试通过匿名子类覆写了 getMsgAuditAccessToken(...) 本身,因此不会覆盖生产实现里的加锁/HTTP 获取/写回 configStorage 等关键逻辑,可能难以捕获后续回归。

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

本 PR 旨在修复企业微信会话存档相关 API 误用应用 secret 获取 access token 导致的权限错误问题,并为会话存档单独引入一套 access token 生命周期管理与调用链路。

Changes:

  • 在配置层(WxCpConfigStorage 及其实现)增加会话存档专用 access token 的存取、过期判断与锁管理接口与实现(内存版完整实现,老的 Redis 直连版仅加了存根)。
  • 在服务层(WxCpService 各种 HTTP 实现类)新增 getMsgAuditAccessToken(boolean),通过会话存档 secret 调用 GET_TOKEN 获取独立 token,并使用锁+双重检查避免并发重复刷新。
  • 在业务层会话存档服务实现中,将 getPermitUserList / getGroupChat / checkSingleAgree 改为使用新的会话存档 access token,并通过 postWithoutToken 手工拼接 access_token,同时新增若干单测用例(但目前对新逻辑的覆盖有限)。

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpServiceGetMsgAuditAccessTokenTest.java 新增会话存档 access token 相关测试类,但当前主要通过覆写方法做“假实现”,实际没有覆盖生产实现的逻辑路径。
weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/BaseWxCpServiceImplTest.java 为匿名子类补齐 getMsgAuditAccessToken 实现,以适配 WxCpService 新增方法。
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpRedisConfigImpl.java 为旧版 Redis 配置实现新增会话存档 access token 相关接口的存根实现,但 getMsgAuditAccessTokenLock 目前返回 null,会导致调用方 NPE。
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpDefaultConfigImpl.java 在默认内存配置中新增会话存档 access token 字段、过期时间与 ReentrantLock,并实现过期判断与更新逻辑。
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpConfigStorage.java 扩展配置存储接口,增加会话存档 access token 相关 get/lock/expire/update 方法。
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceOkHttpImpl.java 基于 OkHttp 的实现增加 getMsgAuditAccessToken,使用 msgAuditSecret 请求 GET_TOKEN,并在本地锁下进行双重检查与 token 更新。
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceJoddHttpImpl.java 基于 Jodd HTTP 的实现增加 getMsgAuditAccessToken,通过 HttpRequest + HttpResponse 获取并更新会话存档 access token。
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceImpl.java 默认(带分布式锁适配)的 Apache HttpClient 实现增加 getMsgAuditAccessToken,沿用已有 access token 刷新模式和锁策略。
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceHttpComponentsImpl.java 基于 HttpComponents5 的实现增加 getMsgAuditAccessToken,在代理配置下调用 GET_TOKEN 并更新 msgAudit access token。
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceApacheHttpClientImpl.java 基于 Apache HttpClient4 的实现增加 getMsgAuditAccessToken,与默认实现保持一致的刷新和异常处理逻辑。
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMsgAuditServiceImpl.java 会话存档业务方法改为通过 getMsgAuditAccessToken 获取专用 token,并使用 postWithoutToken+手动拼接 access_token 调用相关 API。
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java 在服务接口层新增 getMsgAuditAccessToken(boolean forceRefresh),作为会话存档专用 access token 的统一入口。

Comment on lines 37 to 45
WxCpServiceApacheHttpClientImpl service = new WxCpServiceApacheHttpClientImpl() {
@Override
public String getMsgAuditAccessToken(boolean forceRefresh) throws WxErrorException {
// 验证配置是否正确使用
WxCpConfigStorage storage = getWxCpConfigStorage();
assertThat(storage.getMsgAuditSecret()).isEqualTo("testMsgAuditSecret");

// 模拟返回 token
return "mock_msg_audit_access_token";
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里通过匿名子类覆写了 getMsgAuditAccessToken 方法,并在覆写的方法中直接返回固定字符串,因此这个测试并没有真正调用 WxCpServiceApacheHttpClientImpl 的生产实现逻辑,只是验证了配置中 msgAuditSecret 的值,无法覆盖新增加的会话存档 access token 获取和加锁/刷新流程(下面几个实现类的测试方法同理)。建议改为在不覆写 getMsgAuditAccessToken 的前提下,通过 mock HTTP 客户端或使用测试配置,直接调用真实实现,以便在 token 过期判断、锁竞争和错误处理路径上有有效的回归测试。

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

已改进测试代码,新增了对缓存机制、强制刷新、token过期自动刷新和锁机制的测试用例,这些测试会验证真实的 token 生命周期管理逻辑。提交:9d81192


@Override
public Lock getMsgAuditAccessTokenLock() {
return null;
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getMsgAuditAccessTokenLock 当前直接返回 null,而各个 WxCpService 实现里都会无条件调用 configStorage.getMsgAuditAccessTokenLock().lock(),这在使用 WxCpRedisConfigImpl 作为配置存储时会直接触发 NullPointerException,导致会话存档相关接口完全不可用。这里至少应该返回一个实际的 Lock 实例(例如本地 ReentrantLock 或复用现有的分布式锁实现),并与 updateMsgAuditAccessToken / isMsgAuditAccessTokenExpired 的实现配套,否则请在会话存档相关接口上显式防护并给出更明确的错误提示。

Suggested change
return null;
return new ReentrantLock();

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

已修复 NPE 问题,在 WxCpRedisConfigImpl 中添加了 msgAuditAccessTokenLock 字段并返回 ReentrantLock 实例。提交:9d81192

@binarywang
Copy link
Owner

@copilot apply changes based on the comments in this thread

Copilot AI and others added 6 commits January 27, 2026 02:54
Co-authored-by: binarywang <1343140+binarywang@users.noreply.github.com>
Co-authored-by: binarywang <1343140+binarywang@users.noreply.github.com>
Co-authored-by: binarywang <1343140+binarywang@users.noreply.github.com>
Co-authored-by: binarywang <1343140+binarywang@users.noreply.github.com>
Co-authored-by: binarywang <1343140+binarywang@users.noreply.github.com>
Co-authored-by: binarywang <1343140+binarywang@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

weixin-java-cp 4.8.0版本中会话存档开发的bug

2 participants